package cn.pink.core.support.lock;

import io.vertx.core.VertxException;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.shareddata.Lock;
import io.vertx.core.shareddata.impl.LockInternal;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 本地锁
 * @Author: pink
 * @Date: 2022/8/24 18:45
 */
public class LocalLock {
    private class LockWaiter {

        final ContextInternal context;
        final String lockName;

        LockWaiter(ContextInternal context, String lockName) {
            this.lockName = lockName;
            this.context = context;
        }

        void timeout() {
            // Cleanup
            waitersMap.compute(lockName, (s, list) -> {
                int idx;
                if (list == null || (idx = list.indexOf(LocalLock.LockWaiter.this)) == -1) {
                    // Already removed by release()
                    return list;
                } else if (list.size() == 1) {
                    return null;
                } else {
                    int size = list.size();
                    List<LocalLock.LockWaiter> n = new ArrayList<>(size - 1);
                    if (idx > 0) {
                        n.addAll(list.subList(0, idx));
                    }
                    if (idx + 1 < size) {
                        n.addAll(list.subList(idx + 1, size));
                    }
                    return n;
                }
            });
        }

        Lock acquireLock() {
            return new SyncLock(lockName);
        }
    }

    private class SyncLock implements LockInternal {

        final String lockName;
        final AtomicBoolean invoked = new AtomicBoolean();

        SyncLock(String lockName) {
            this.lockName = lockName;
        }

        @Override
        public void release() {
            if (invoked.compareAndSet(false, true)) {
                nextWaiter(lockName);
            }
        }

        @Override
        public int waiters() {
            List<LocalLock.LockWaiter> waiters = waitersMap.get(lockName);
            return waiters == null ? 0 : waiters.size() - 1;
        }
    }

    // Value should never be modified
    private final ConcurrentMap<String, List<LocalLock.LockWaiter>> waitersMap = new ConcurrentHashMap<>();

    public synchronized Lock acquire(ContextInternal context, String name, long timeout) {
        LocalLock.LockWaiter lockWaiter = new LocalLock.LockWaiter(context, name);
        waitersMap.compute(name, (s, list) -> {
            List<LocalLock.LockWaiter> result;
            if (list != null) {
                result = new ArrayList<>(list.size() + 1);
                result.addAll(list);
            } else {
                result = new ArrayList<>(1);
            }
            result.add(lockWaiter);
            return result;
        });

        Lock lock = null;
        long start = System.currentTimeMillis();

        do {
            if (waitersMap.get(name).size() == 1) {
                lock = waitersMap.get(name).get(0).acquireLock();
            }
        } while (lock == null && (System.currentTimeMillis() - start) < timeout);

        if (lock != null) {
            return lock;
        } else {
            lockWaiter.timeout();

            throw new VertxException("Timed out waiting to get lock " + name);
        }
    }

    private void nextWaiter(String lockName) {
        waitersMap.compute(lockName, (s, list) -> list == null || list.size() == 1 ? null : new ArrayList<>(list.subList(1, list.size())));
    }
}
